<meta charset="utf-8">
(#) HashMap can be replaced with SparseArray

!!! WARNING: HashMap can be replaced with SparseArray
   This is a warning.

Id
:   `UseSparseArrays`
Summary
:   HashMap can be replaced with SparseArray
Severity
:   Warning
Category
:   Performance
Platform
:   Android
Vendor
:   Android Open Source Project
Feedback
:   https://issuetracker.google.com/issues/new?component=192708
Since
:   Initial
Affects
:   Kotlin and Java files
Editing
:   This check runs on the fly in the IDE editor
Implementation
:   [Source Code](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:lint/libs/lint-checks/src/main/java/com/android/tools/lint/checks/JavaPerformanceDetector.java)
Tests
:   [Source Code](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/JavaPerformanceDetectorTest.java)
Copyright Year
:   2012

For maps where the keys are of type integer, it's typically more
efficient to use the Android `SparseArray` API. This check identifies
scenarios where you might want to consider using `SparseArray` instead
of `HashMap` for better performance.

This is **particularly** useful when the value types are primitives like
ints, where you can use `SparseIntArray` and avoid auto-boxing the
values from `int` to `Integer`.

If you need to construct a `HashMap` because you need to call an API
outside of your control which requires a `Map`, you can suppress this
warning using for example the `@SuppressLint` annotation.

(##) Example

Here is an example of lint warnings produced by this check:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~text
src/test/pkg/JavaPerformanceTest.java:182:Warning: Use new
SparseIntArray(...) instead for better performance [UseSparseArrays]
    new SparseArray&lt;Integer&gt;(); // Use SparseIntArray instead
    --------------------------
src/test/pkg/JavaPerformanceTest.java:184:Warning: Use new
SparseBooleanArray(...) instead for better performance
[UseSparseArrays]
    new SparseArray&lt;Boolean&gt;(); // Use SparseBooleanArray instead
    --------------------------
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Here is the source file referenced above:

`src/test/pkg/JavaPerformanceTest.java`:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~java linenumbers
package test.pkg;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
import android.graphics.Rect;
import android.graphics.Shader.TileMode;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.widget.Button;
import java.util.HashMap;
import java.util.Map;

/** Some test data for the JavaPerformanceDetector */
@SuppressWarnings("unused")
public class JavaPerformanceTest extends Button {
    public JavaPerformanceTest(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    private Rect cachedRect;

    @Override
    protected void onDraw(android.graphics.Canvas canvas) {
        super.onDraw(canvas);

        // Various allocations:
        new String("foo");
        String s = new String("bar");

        // This one should not be reported:
        @SuppressLint("DrawAllocation")
        Integer i = new Integer(5);

        // Cached object initialized lazily: should not complain about these
        if (cachedRect == null) {
            cachedRect = new Rect(0, 0, 100, 100);
        }
        if (cachedRect == null || cachedRect.width() != 50) {
            cachedRect = new Rect(0, 0, 50, 100);
        }

        boolean b = Boolean.valueOf(true); // auto-boxing
        sample(1, 2);

        // Non-allocations
        super.animate();
        sample2(1, 2);
        int x = 4 + '5';

        // This will involve allocations, but we don't track
        // inter-procedural stuff here
        someOtherMethod();
    }

    void sample(Integer foo, int bar) {
        sample2(foo, bar);
    }

    void sample2(int foo, int bar) {
    }

    void someOtherMethod() {
        // Allocations are okay here
        new String("foo");
        String s = new String("bar");
        boolean b = Boolean.valueOf(true); // auto-boxing
    }

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec,
                             boolean x) { // wrong signature
        new String("not an error");
    }

    protected void onMeasure(int widthMeasureSpec) { // wrong signature
        new String("not an error");
    }

    protected void onLayout(boolean changed, int left, int top, int right,
                            int bottom, int wrong) { // wrong signature
        new String("not an error");
    }

    protected void onLayout(boolean changed, int left, int top, int right) {
        // wrong signature
        new String("not an error");
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right,
                            int bottom) {
        new String("flag me");
    }

    @SuppressWarnings("null") // not real code
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        new String("flag me");

        // Forbidden factory methods:
        Bitmap.createBitmap(100, 100, null);
        android.graphics.Bitmap.createScaledBitmap(null, 100, 100, false);
        BitmapFactory.decodeFile(null);
        Canvas canvas = null;
        canvas.getClipBounds(); // allocates on your behalf
        canvas.getClipBounds(null); // NOT an error

        final int layoutWidth = getWidth();
        final int layoutHeight = getHeight();
        if (mAllowCrop && (mOverlay == null || mOverlay.getWidth() != layoutWidth ||
                mOverlay.getHeight() != layoutHeight)) {
            mOverlay = Bitmap.createBitmap(layoutWidth, layoutHeight, Bitmap.Config.ARGB_8888);
            mOverlayCanvas = new Canvas(mOverlay);
        }

        if (widthMeasureSpec == 42) {
            throw new IllegalStateException("Test"); // NOT an allocation
        }

        // More lazy init tests
        boolean initialized = false;
        if (!initialized) {
            new String("foo");
            initialized = true;
        }

        // NOT lazy initialization
        if (!initialized || mOverlay == null) {
            new String("foo");
        }
    }

    void factories() {
        Integer i1 = new Integer(42);
        Long l1 = new Long(42L);
        Boolean b1 = new Boolean(true);
        Character c1 = new Character('c');
        Float f1 = new Float(1.0f);
        Double d1 = new Double(1.0);

        // The following should not generate errors:
        Object i2 = new foo.bar.Integer(42);
        Integer i3 = Integer.valueOf(42);
    }

    private boolean mAllowCrop;
    private Canvas mOverlayCanvas;
    private Bitmap mOverlay;
private abstract class JavaPerformanceTest1 extends JavaPerformanceTest {
    @Override
    public void layout(int l, int t, int r, int b) {
        // Using "this." to reference fields
        if (this.shader == null)
            this.shader = new LinearGradient(0, 0, getWidth(), 0, GRADIENT_COLORS, null,
                    TileMode.REPEAT);
    }
} private abstract class JavaPerformanceTest2 extends JavaPerformanceTest {
        @Override
    public void layout(int l, int t, int r, int b) {
        int width = getWidth();
        int height = getHeight();

        if ((shader == null) || (lastWidth != width) || (lastHeight != height))
        {
            lastWidth = width;
            lastHeight = height;

            shader = new LinearGradient(0, 0, width, 0, GRADIENT_COLORS, null, TileMode.REPEAT);
        }
    }
} private abstract class JavaPerformanceTest3 extends JavaPerformanceTest {
    @Override
    public void layout(int l, int t, int r, int b) {
        if ((shader == null) || (lastWidth != getWidth()) || (lastHeight != getHeight())) {
        }
    }
}
    public void inefficientSparseArray() {
        new SparseArray<Integer>(); // Use SparseIntArray instead
        new SparseArray<Long>();    // Use SparseLongArray instead
        new SparseArray<Boolean>(); // Use SparseBooleanArray instead
        new SparseArray<Object>();  // OK
    }

    public void longSparseArray() { // but only minSdkVersion >= 17 or if has v4 support lib
        Map<Long, String> myStringMap = new HashMap<Long, String>();
    }

    public void byteSparseArray() { // bytes easily apply to ints
        Map<Byte, String> myByteMap = new HashMap<Byte, String>();
    }

    protected LinearGradient shader;
    protected int lastWidth;
    protected int lastHeight;
    protected int[] GRADIENT_COLORS;

    private static class foo {
        private static class bar {
            private static class Integer {
                public Integer(int val) {
                }
            }
        }
    }
    public JavaPerformanceTest() {
        super(null);
    }
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

You can also visit the
[source code](https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:lint/libs/lint-tests/src/test/java/com/android/tools/lint/checks/JavaPerformanceDetectorTest.java)
for the unit tests for this check to see additional scenarios.

The above example was automatically extracted from the first unit test
found for this lint check, `JavaPerformanceDetector.test`.
To report a problem with this extracted sample, visit
https://issuetracker.google.com/issues/new?component=192708.

(##) Suppressing

You can suppress false positives using one of the following mechanisms:

* Using a suppression annotation like this on the enclosing
  element:

  ```kt
  // Kotlin
  @Suppress("UseSparseArrays")
  fun method() {
     problematicStatement()
  }
  ```

  or

  ```java
  // Java
  @SuppressWarnings("UseSparseArrays")
  void method() {
     problematicStatement();
  }
  ```

* Using a suppression comment like this on the line above:

  ```kt
  //noinspection UseSparseArrays
  problematicStatement()
  ```

* Using a special `lint.xml` file in the source tree which turns off
  the check in that folder and any sub folder. A simple file might look
  like this:
  ```xml
  &lt;?xml version="1.0" encoding="UTF-8"?&gt;
  &lt;lint&gt;
      &lt;issue id="UseSparseArrays" severity="ignore" /&gt;
  &lt;/lint&gt;
  ```
  Instead of `ignore` you can also change the severity here, for
  example from `error` to `warning`. You can find additional
  documentation on how to filter issues by path, regular expression and
  so on
  [here](https://googlesamples.github.io/android-custom-lint-rules/usage/lintxml.md.html).

* In Gradle projects, using the DSL syntax to configure lint. For
  example, you can use something like
  ```gradle
  lintOptions {
      disable 'UseSparseArrays'
  }
  ```
  In Android projects this should be nested inside an `android { }`
  block.

* For manual invocations of `lint`, using the `--ignore` flag:
  ```
  $ lint --ignore UseSparseArrays ...`
  ```

* Last, but not least, using baselines, as discussed
  [here](https://googlesamples.github.io/android-custom-lint-rules/usage/baselines.md.html).

<!-- Markdeep: --><style class="fallback">body{visibility:hidden;white-space:pre;font-family:monospace}</style><script src="markdeep.min.js" charset="utf-8"></script><script src="https://morgan3d.github.io/markdeep/latest/markdeep.min.js" charset="utf-8"></script><script>window.alreadyProcessedMarkdeep||(document.body.style.visibility="visible")</script>